package asteroids.entity;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;

import org.apache.log4j.Logger;

import asteroids.engine.GameState;
import asteroids.engine.Utils;
import asteroids.engine.ai.Pilot;
import asteroids.engine.ai.RandomPilot;
import asteroids.entity.equipment.Equipment;
import asteroids.entity.weapon.Bullet;
import asteroids.entity.weapon.Weapon;

/**
 * Defines the base attributes of a ship. Ships: - have a position - can turn
 * and move/accelerate - have energy - can hold weapons and equipment - have
 * lives, and will respawn upon dying (with temporary invincibility) if lives
 * remain - manage Bullets (though this may be outsourced to a Weapon class) -
 * have a Pilot to issue orders
 * 
 * This class also defines the update 'stages' for ships: stage 1 does position
 * updating/internal management; stage 2 updates bullets and bullet counts;
 * Stage 3 updates equipment. Updating each stage for all ships (in ShipManager)
 * before going to the next stage ensures consistent interactions
 * 
 * @author whitecd1
 * 
 */
public abstract class Ship extends GameObject implements asteroids.engine.ObjectManager<Bullet>
{
    private static Logger logger = Logger.getLogger(Ship.class);

    // these are public, since they can't change the ship's behavior - they are
    // set, but never read within Ship
    public double speed = 0;
    public double direction = 0;

    // game management controls
    private int lives;
    private int respawnTimeLeft;
    private final int GRACE_PERIOD = 2 * Utils.FRAMES_PER_SECOND;

    private double respawn_x;
    private double respawn_y;

    private int max_energy;
    private double energy;

    private final double MAX_INTEGRITY;

    private Color shipColor = Color.WHITE;
    /** stores ShipIcing Permanently because icing is cleared upon death **/
    private ArrayList<ShipIcing> bling;

    private ArrayList<Bullet> bullets; // ArrayList for possible run-time
                                       // expansion?
    protected ArrayList<Equipment> equipment; // for equipment that runs by
                                              // itself w/o player
                                              // intervention
    protected Weapon weapon; // for the main weapon
    protected SpecialAdapter special;

    protected String name;

    protected Pilot pilot;
    protected int orders;
    protected boolean weaponEngaged;

    public Ship(String n, double x, double y, Utils.PolarCoordinate[] shipShape,
            String affiliation, int maxEnergy, double integrity, int mass)
    {
        super(x, y, shipShape, GameObject.ObjectType.SHIP, affiliation, mass);

        name = n;
        respawn_x = x;
        respawn_y = y;

        max_energy = maxEnergy;
        energy = max_energy;

        this.integrity = integrity;
        MAX_INTEGRITY = integrity;

        bling = new ArrayList<ShipIcing>();
        bullets = new ArrayList<Bullet>();
        equipment = new ArrayList<Equipment>();

        pilot = new RandomPilot(); // probably be a MonteCarlo as the
                                   // default
                                   // eventually
    }

    public Ship(String n, double x, double y, Utils.PolarCoordinate[] shipShape, String affiliation)
    {
        this(n, x, y, shipShape, affiliation, 20, 1, 5);
    }

    public void setPilot(Pilot pilot)
    {
        this.pilot = pilot;
    }

    /**
     * Update Phase I: Implemented in sub-classes to do reasoning and control
     * Used here only to manage internal states of things
     * 
     * @param state
     */
    public void update(GameState state)
    {
        logger.trace("updating " + name);
        super.update(); // apply movements, update icing

        // update speed and direction
        Utils.PolarCoordinate velocity = Utils.polarFromCartesian(Utils.createCartesianCoordinate(
                x_speed, y_speed));
        direction = velocity.getRotation();
        speed = velocity.getRadius();

        // decrement the shield timer if it is greater than zero
        respawnTimeLeft = (respawnTimeLeft > 0) ? respawnTimeLeft - 1 : respawnTimeLeft;

        if( respawnTimeLeft == 0 )
        {
            invincible = false;
            objectType = GameObject.ObjectType.SHIP;
        }

        if( !alive && icing.size() == 0 && lives > 0 && bullets.size() == 0 )
        {
            respawn();
        }
    }

    /**
     * Update Phase II: Move bullets, clean up expired bullets, adjust counts.
     */
    public void updateWeapons(GameState state)
    {
        for( int i = bullets.size() - 1; i >= 0; i-- )
        {
            Bullet b = bullets.get(i);

            if( !b.isAlive() )
            {
                bullets.remove(i);
                b = null;
            }
            else
            {
                b.update();
            }
        }
        if( weaponEngaged && weapon != null )
        {
            weapon.FIRE(state);
            weaponEngaged = false;
        }
    }

    /**
     * Update Phase III: update the state of any onboard equipment. This is done
     * after the bullet update phase to ensure that bullets are cleaned up
     * gracefully after being snuffed by various equipment types
     * 
     * @param state
     */
    public void updateEquipment(GameState state)
    {
        for( int i = equipment.size() - 1; i >= 0; i-- )
        {
            Equipment e = equipment.get(i);
            if( alive || e.updateAfterDeath() )
            {
                e.update(state);
            }
            if( !e.isActive() )
            {
                equipment.remove(e);
            }
        }
    }

    /** @deprecated */
    public void FIRE(GameState state)
    {
        if( weapon != null )
        {
            // weapon.engage();
        }
    }

    // /**
    // * simply calls GameObject.update() and does bullet management. Subclasses
    // can deal with their control logic and
    // * call this to do movement/bullet stuff.
    // */
    public void update()
    {
        logger.warn("Calling Ship.update() instead of Ship.update(state)");
        super.update();
    }

    public String getName()
    {
        return name;
    }

    public void add(Bullet b)
    {
        bullets.add(b);
    }

    public void addAll(ArrayList<? extends Bullet> bs)
    {
        bullets.addAll(bs);
    }

    public void addLives(int num)
    {
        lives += num;
    }

    public ArrayList<Bullet> getObjects()
    {
        return bullets;
    }

    @Override
    public void collide(GameObject other, double power)
    {
        super.collide(other, power);

        // // Push the other away, if it hasn't died
        // other.addVelocity(Utils.createPolarCoordinate(
        // Utils.pointDirection(getXPosition(), getYPosition(),
        // other.getXPosition(),
        // other.getYPosition()), speed));

        if( !isAlive() )
        {
            logger.trace("adding shrapnel");
            // add shrapnel effects.
            for( int k = 0; k < (int) (Math.round(Math.random() * 6 + 4)); k++ )
            {
                int s = (int) (Math.random() * (max_radius - 4)) + 4;
                Utils.CartesianCoordinate coord = Utils.cartesianFromPolar(Utils
                        .createPolarCoordinate(Math.random() * 360 + 1, max_radius / 2));
                Utils.PolarCoordinate[] icingShape = { Utils.createPolarCoordinate(0, s / 2),
                        Utils.createPolarCoordinate(180, s / 2) };
                Icing i = new Icing(x_position + coord.getXComponent(), y_position
                        + coord.getYComponent(), Utils.FRAMES_PER_SECOND, icingShape,
                        GameObject.ObjectType.ICING);
                i.setRotation(Math.random() * 360);
                i.setRotationSpeed(Math.random() * 360 / (1.5 * Utils.FRAMES_PER_SECOND) + 2.5);
                i.setVelocity(Math.random() * (100 / Utils.FRAMES_PER_SECOND)
                        - (50 / Utils.FRAMES_PER_SECOND), Math.random()
                        * (100 / Utils.FRAMES_PER_SECOND) - (50 / Utils.FRAMES_PER_SECOND));
                icing.add(i);
            }
        }
    }

    @Override
    public double getCollisionPower(GameObject other)
    {
        return 1;
    }

    protected void respawn()
    {
        lives--;
        y_position = respawn_y;
        x_position = respawn_x;
        x_speed = 0;
        y_speed = 0;
        rotation = 270;
        rot_speed = 0;
        alive = true;
        energy = max_energy;
        integrity = MAX_INTEGRITY;
        grantTemporaryInvincibility();
        for( Equipment e : equipment )
        {
            e.respawn();
        }
        for( ShipIcing s : bling )
        {
            icing.add(s);
        }
    }

    /**
     * Returns the number of frames for which the ship's respawn invincibility
     * will still be in effect. Returns 0 if the ship no longer has respawn
     * invincibility.
     * 
     * @return
     */
    public int getRespawnTimeLeft()
    {
        return respawnTimeLeft;
    }

    public void grantTemporaryInvincibility()
    {
        objectType = GameObject.ObjectType.ICING; // to prevent crushing
                                                  // Asteroids
        invincible = true;
        respawnTimeLeft = GRACE_PERIOD;
    }

    public void makeInvincible()
    {
        invincible = true;
    }

    public void modifyEnergyBy(double e)
    {
        energy = Math.min(energy + e, max_energy);
    }

    public double getEnergy()
    {
        return energy;
    }

    public int getMaxEnergy()
    {
        return max_energy;
    }

    /**
     * nulls any allocated resources
     */
    public void destroy()
    {
        super.destroy();
        bullets.clear();
    }

    public void clear()
    {
        bullets.clear();
    }

    public void addBling(ShipIcing s)
    {
        bling.add(s);
        icing.add(s);
    }

    /** @deprecated */
    public void erase(Graphics g)
    {
        super.erase(g);
        for( Equipment e : equipment )
        {
            e.erase(g);
        }
        for( Bullet b : bullets )
        {
            b.erase(g);
        }
    }

    public void draw(Graphics g)
    {
        if( respawnTimeLeft % 2 != 0 )
        {
            color = Color.DARK_GRAY;
        }
        else
        {
            color = shipColor;
        }
        super.draw(g);
        for( Equipment e : equipment )
        {
            e.draw(g);
        }
        for( Bullet b : bullets )
        {
            if( b.isAlive() )
            {
                b.draw(g);
            }
        }
    }

    public void setColor(Color c)
    {
        shipColor = c;
    }

    public boolean isActive()
    {
        return !(lives == 0 && !alive && icing.size() == 0 && bullets.size() == 0);
    }

    /**
     * add an item of equipment to the ship
     * 
     * @param e
     */
    public void equip(Equipment e)
    {
        equipment.add(e);
    }

    /**
     * give the ship a weapon
     * 
     * @param w
     */
    public void arm(Weapon w)
    {
        weapon = w;
    }

    /**
     * sets w as the ship's 'special' using a SpecialAdapter
     */
    public void setSpecial(Weapon w)
    {
        if( special != null )
        {
            equipment.remove(special);
        }
        special = new SpecialAdapter(w);
        equipment.add(special);
    }

    /**
     * sets e as the ship's 'special' using a SpecialAdapter
     */
    public void setSpecial(Equipment e)
    {
        if( special != null )
        {
            equipment.remove(special);
        }
        special = new SpecialAdapter(e);
        equipment.add(special);
    }

    public void addIcing(Icing i)
    {
        icing.add(i);
    }

    public String toString()
    {
        return name;
    }

    public Pilot getPilot()
    {
        return pilot;
    }

    public double getDirection()
    {
        return direction;
    }

    public double getSpeed()
    {
        return speed;
    }
}
